原簡體中文教程連結: Introduction.《Terraform入門教程》
請注意,本節介紹的透過 moved
區塊進行模組重構的功能是從 Terraform v1.1 開始被引入的。如果要在先前的版本進行這樣的操作,必須透過 terraform state mv
指令來完成。
對一些旨在被人復用的老模組來說,最初的模組結構和資源名稱可能會逐漸變得不再合適。例如,我們可能會發現將先前的一個子模組分割成兩個單獨的模組會更合理,這需要將現有資源的子集移到新的模組中。
Terraform 將先前的狀態與新程式碼進行比較,資源與每個模組或資源的唯一位址相關聯。因此,預設情況下,移動或重新命名物件會被 Terraform 理解為銷毀舊地址的物件並在新地址建立新的物件。
當我們在程式碼中新增 moved
區塊以記錄我們移動或重新命名物件過去的地址時,Terraform 會將舊地址的現有物件視為現在屬於新地址。
moved
區塊只包含 from
和 to
參數,沒有名稱:
moved {
from = aws_instance.a
to = aws_instance.b
}
上面的例子演示了模組先前版本中的 aws_instance.a
如今以 aws_instance.b
的名字存在。
在為 aws_instance.b
建立新的變更計畫之前,Terraform 會先檢查目前狀態中是否存在位址為 aws_instance.a
的記錄。如果存在該記錄,Terraform 會將其重命名為 aws_instance.b
然後繼續建立變更計劃。最終產生的變更計劃中該物件就好像一開始就是以 aws_instance.b
的名字被創建的,防止它在執行變更時被刪除。
from
和 to
的位址使用特殊的位址語法,該語法允許選定模組、資源以及子模組中的資源。以下是幾種不同的重構場景中所需要的位址語法:
考慮模組程式碼中這樣一個資源:
resource "aws_instance" "a" {
count = 2
# (resource-type-specific configuration)
}
第一次套用該程式碼時 Terraform 會建立 aws_instance.a[0]
以及 aws_instance.a[1]
。
如果隨後我們修改了該資源的名稱,並且把舊名字記錄在一個 moved
區塊裡:
resource "aws_instance" "b" {
count = 2
# (resource-type-specific configuration)
}
moved {
from = aws_instance.a
to = aws_instance.b
}
當下一次應用程式使用了該模組的程式碼時,Terraform 會把所有位址為 aws_instance.a
的物件看作是一開始就以 aws_instance.b
的名字創建的:aws_instance.a[0]
會被看作是 aws_instance.b[0]
,aws_instance.a[1]
會被看作是 aws_instance.b[1]
。
新建立的模組實例中,因為從來就不存在 aws_instance.a
,於是會忽略 moved
區塊而像通常那樣直接建立 aws_instance.b[0]
以及 aws_instance.b[1
]。
一開始程式碼中有這樣一個單一實例資源:
resource "aws_instance" "a" {
# (resource-type-specific configuration)
}
應用程式碼會使得 Terraform 建立了一個位址為 aws_instance.a
的資源物件。
隨後我們想要在該資源上新增 for_each
來建立多個實例。為了保持先前關聯到 aws_instance.a
的資源物件不受影響,我們必須新增一個 moved
區塊來指定新程式碼中原先的物件實例所關聯的鍵是什麼:
locals {
instances = tomap({
big = {
instance_type = "m3.large"
}
small = {
instance_type = "t2.medium"
}
})
}
resource "aws_instance" "a" {
for_each = local.instances
instance_type = each.value.instance_type
# (other resource-type-specific configuration)
}
moved {
from = aws_instance.a
to = aws_instance.a["small"]
}
上面的程式碼會防止 Terraform 在變更計畫中銷毀已經存在的 aws_instance.a
對象,並且將其視為以 aws_instance.a["small"]
的位址建立的。
當 moved
區塊的兩個位址中的至少一個包含實例鍵時,如上例中的 ["small"]
,Terraform 將這兩個位址理解為引用資源的特定實例而不是整個資源。這表示您可以使用 moved
在按鍵之間切換以及在 count
、for_each
之間切換時新增和刪除鍵。
下面的範例示範了幾種其他類似的記錄了資源實例鍵變更的合法 moved
區塊:
# Both old and new configuration used "for_each", but the
# "small" element was renamed to "tiny".
moved {
from = aws_instance.b["small"]
to = aws_instance.b["tiny"]
}
# The old configuration used "count" and the new configuration
# uses "for_each", with the following mappings from
# index to key:
moved {
from = aws_instance.c[0]
to = aws_instance.c["small"]
}
moved {
from = aws_instance.c[1]
to = aws_instance.c["tiny"]
}
# The old configuration used "count", and the new configuration
# uses neither "count" nor "for_each", and you want to keep
# only the object at index 2.
moved {
from = aws_instance.d[2]
to = aws_instance.d
}
注意:當我們在原先沒有宣告 count
的資源上新增 count
時,Terraform 會自動將原先的物件移到第 0 個位置,除非我們透過一個 moved
區塊明確宣告該資源。然而,我們建議使用 moved
區塊明確聲明資源的移動,使得讀者在未來閱讀模組的程式碼時更清楚地了解這些變更。
我們可以用類似重命名資源的方式來重新命名對模組的呼叫。假設我們開始用以下程式碼呼叫一個模組:
module "a" {
source = "../modules/example"
# (module arguments)
}
當套用程式碼時,Terraform 會在模組內宣告的資源路徑前面加上一個模組路徑前綴 module.a
。比方說,模組內的 aws_instance.example
的完整位址為 module.a.aws_instance.example
。
如果我們隨後打算修改模組名稱,我們可以直接修改 module
區塊的標籤,並且在一個 moved
區塊內部記錄該變更:
module "b" {
source = "../modules/example"
# (module arguments)
}
moved {
from = module.a
to = module.b
}
當下一次套用包含該模組呼叫的程式碼時,Terraform 會將所有路徑前綴為 module.a
的物件視為從一開始就是以 module.b
為前綴建立的。module.a.aws_instance.example
會被看作是 module.b.aws_instance.example
。
此範例中的 moved
區塊中的兩個位址都代表對模組的調用,而 Terraform 識別出將原始模組位址中所有的資源移至新的模組位址中。如果該模組聲明時使用了 count
或是 for_each
,那麼該移動也將被應用於所有的實例上,不需要逐個指定。
考慮一下單一實例的模組:
module "a" {
source = "../modules/example"q
# (module arguments)
}
應用該段程式碼會導致 Terraform 建立的資源位址都擁有 module.a
的前綴。
隨後如果我們可能需要再透過新增 count
來建立多個資源實例。為了保留先前的 aws_instance.a
實例不受影響,我們可以新增一個 moved
區塊來設定在新程式碼中該實例的對應的鍵。
module "a" {
source = "../modules/example"
count = 3
# (module arguments)
}
moved {
from = module.a
to = module.a[2]
}
上面的程式碼引導 Terraform 將所有 module.a
中的資源視為從一開始就是以 module.a[2]
的前綴建立的。結果就就是,Terraform 產生的變更計畫中只會創建 module.a[0]
以及 module.a[1]
。
當 moved
區塊的兩個位址中的至少一個包含實例鍵時,例如上面範例中的 [2]
那樣,Terraform 會理解將這兩個位址理解為對模組的特定實例的呼叫而非對模組所有實例的呼叫。這意味著我們可以使用 moved
區塊在不同鍵之間切換來新增或是刪除鍵,該機制可用於 count
和 for_each
,或刪除模組上的這種聲明。
隨著模組提供的功能越來越多,最終模組可能變得過大而不得不將之拆分成兩個獨立的模組。
我們來看看下面的這個例子:
resource "aws_instance" "a" {
# (other resource-type-specific configuration)
}
resource "aws_instance" "b" {
# (other resource-type-specific configuration)
}
resource "aws_instance" "c" {
# (other resource-type-specific configuration)
}
我們可以將該模組分割為三個部分:
aws_instance.a
現在歸屬於模組 "x"。aws_instance.b
也屬於模組 "x"。aws_instance.c
現在歸屬於模組 "y"。resource
塊:# module "x"
resource "aws_instance" "a" {
# (other resource-type-specific configuration)
}
resource "aws_instance" "b" {
# (other resource-type-specific configuration)
}
# module "y"
resource "aws_instance" "c" {
# (other resource-type-specific configuration)
}
而原有模組則被修改成只包含有向下相容邏輯的墊片,呼叫兩個新模組,並使用 moved
塊定義哪些資源被移到新模組中去了:
module "x" {
source = "../modules/x"
# ...
}
module "y" {
source = "../modules/y"
# ...
}
moved {
from = aws_instance.a
to = module.x.aws_instance.a
}
moved {
from = aws_instance.b
to = module.x.aws_instance.b
}
moved {
from = aws_instance.c
to = module.y.aws_instance.c
}
當一個原始模組的呼叫者升級模組版本到這個「墊片」版本時,Terraform 會注意到這些 moved
區塊,並將那些關聯到舊地址的資源物件看作是從一開始就是由新模組創建的那樣。
這個模組的新用戶可以選擇使用這個墊片模組,或是獨立呼叫兩個新模組。我們需要通知舊模組的現有用戶舊模組已被廢棄,他們將來的開發中需要獨立使用這兩個新模組。
多模組重構的場景是不多見的,因為它違反了父模組將其子模組視為黑盒的典型規則,不知道在其中聲明了哪些資源。這種妥協的前提是假設所有這三個模組都由同一個人維護並分佈在一個模組包中。
為避免獨立模組之間的耦合,Terraform 只允許宣告在同一個目錄下的模組間的移動。換句話說,Terraform 不允許將資源移到一個 source
位址不是本機路徑的模組。
Terraform 使用定義 moved
區塊的模組實例的位址的位址來解析 moved
區塊中的相對位址。例如,如果上面的原始模組已經是名為 module.original
的子模組,則原模組中對 module.x.aws_instance.a
的引用在根模組中將被解析為 module.original.module.x.aws_instance.a
。一個模組只能針對它本身或是它的子模組中的資源聲明 moved
區塊。
如果需要引用帶有 count
或 for_each
元參數的模組中的資源,則必須指定要使用的特定實例鍵以符合資源配置的新位置:
moved {
from = aws_instance.example
to = module.new[2].aws_instance.example
}
隨著時間的推移,一些舊模組可能會累積大量 moved
塊。
刪除 moved
區塊通常是一種破壞性變更,因為刪除後所有使用舊地址引用的物件都將被刪除而不是被移動。我們強烈建議保留歷史上所有的 moved
區塊來保存使用者從任意版本升級到目前版本的升級路徑資訊。
如果我們決定要刪除 moved
塊,需要謹慎行事。對於組織內部的私有模組來說刪除 moved
區塊可能是安全的,因為我們可以確認所有使用者都已經使用新版本模組程式碼運行過 terraform apply
了。
如果我們需要多次重命名或是移動一個對象,我們建議使用串聯的 moved
區塊來記錄完整的變更訊息,新的區塊引用已有的區塊:
moved {
from = aws_instance.a
to = aws_instance.b
}
moved {
from = aws_instance.b
to = aws_instance.c
}
像這樣記錄下移動的序列可以使 aws_instance.a
以及 aws_instance.b
兩種地址的資源都成功更新,Terraform 會將他們視為從一開始就是以 aws_instance.c
的地址創建的。
原簡體中文教程連結: Introduction.《Terraform入門教程》